﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Bitboard = System.UInt64;

namespace OliveChess
{
    

    class BitboardHelper
    {
        // Const bitboards
        public Bitboard[,] rayAttacks;
        public Bitboard[,] arrPawnAttacks;
        public Bitboard[] arrKnightAttacks;
        public Bitboard[] arrKingAttacks;
        
        private static BitboardHelper instance;
        
        private BitboardHelper() {
            rayAttacksInit();
            arrKingAttacksInit();
            arrPawnAttacksInit();
            arrKnightsAttacksInit();
        }

        private void arrKingAttacksInit()
        {
            arrKingAttacks = new Bitboard[64];

            //int[] dir8 = { 8, 9, 1, -7, -8, -9, -1, 7 };
            int[] mbdir8 = { 10, 11, 1, -9, -10, -11, -1, 9 };


            //int dep = dir8[dir];
            
            for (int sq = 0; sq < 64; sq++)
            {
                for (int dir = 0; dir < 8; dir++)
                {
                    int mbdep = mbdir8[dir];
                    int target = Mailbox.mailbox[Mailbox.mailbox64[sq] + mbdep];
                    if (target != -1)
                        setBit(ref arrKingAttacks[sq], target);
                    
                }
                //DebugDisplay(arrKingAttacks[sq]);
            }
        }

        private void arrKnightsAttacksInit()
        {
            arrKnightAttacks = new Bitboard[64];

            //int[] dir8 = { 8, 9, 1, -7, -8, -9, -1, 7 };
            int[] mbdir8 = { 19, 21, 12, -8, -19, -21, -12,8};


            //int dep = dir8[dir];

            for (int sq = 0; sq < 64; sq++)
            {
                for (int dir = 0; dir < 8; dir++)
                {
                    int mbdep = mbdir8[dir];
                    int target = Mailbox.mailbox[Mailbox.mailbox64[sq] + mbdep];
                    if (target != -1)
                        setBit(ref arrKnightAttacks[sq], target);

                }
                //DebugDisplay(arrKnightAttacks[sq]);
            }
        }

        private void arrPawnAttacksInit()
        {
            arrPawnAttacks = new Bitboard[2,64];
            //int[] dir8 = {8,9,1,-7,-8,-9,-1,7};
            //int[] mbdir8 = { 10, 11, 1, -9, -10, -11, -1, 9 };

            for (int color = 0; color<=1 ; color++)
            {
                int colorOffset = (color == 0) ? 10 : -10;
                //int dep = dir8[dir];
                for (int sq = 0; sq < 64; sq++)
                {
                    int leftAttackIndex = Mailbox.mailbox[Mailbox.mailbox64[sq] - 1 + colorOffset];
                    int rightAttackIndex = Mailbox.mailbox[Mailbox.mailbox64[sq] + 1 + colorOffset];

                    if(leftAttackIndex != -1)
                        setBit(ref arrPawnAttacks[color, sq], leftAttackIndex);
                    if (rightAttackIndex != -1)
                        setBit(ref arrPawnAttacks[color, sq], rightAttackIndex);

                    //DebugDisplay(arrPawnAttacks[color, sq]);
                }

            }
        }

        private void rayAttacksInit()
        {
            rayAttacks = new Bitboard[8,64];

            //int[] dir8 = {8,9,1,-7,-8,-9,-1,7};
            int[] mbdir8 = { 10, 11, 1, -9, -10, -11, -1, 9 };

            for(int dir=0;dir<8;dir++)
            {
                //int dep = dir8[dir];
                int mbdep = mbdir8[dir];
                for(int sq=0;sq<64;sq++) 
                {

                    for (int target = Mailbox.mailbox[Mailbox.mailbox64[sq] + mbdep]; target != -1; target = Mailbox.mailbox[Mailbox.mailbox64[target] + mbdep])
                        setBit(ref rayAttacks[dir, sq], target);
                    //DebugDisplay(rayAttacks[dir, sq]);
                }
            }
        }

        private void setBit(ref Bitboard p, int target)
        {
            p |= ((Bitboard)1 << target);
        }

        private void unsetBit(ref Bitboard p, int target)
        {
            p &= ~((Bitboard)1 << target);
        }


        public static BitboardHelper Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new BitboardHelper();
                }
                return instance;
            }
        }


        // simplest home-made BitScanReverse (to replace intrinsec x86 function)
        // (prefer the intrinsec optimized method )
        public static bool BitScanForward(out int index, Bitboard mask)
        {
            index = 0;
            while ((mask & 1)==0)
            {
                mask >>= 1;
                index++;
            }
            return mask != 0;
        }
        public static bool BitScanReverse(out int index, Bitboard mask)
        {
            index = 0;
            while (mask > 1)
            {
                mask >>= 1;
                index++;
            }
            return mask == 1;
        }
 
        public static int BitCount (Bitboard value) {
            int count = 0;
            while (value > 0) {           // until all bits are zero
                if ((value & 1) == 1)     // check lower bit
                    count++;
                value >>= 1;              // shift bits, removing lower bit
            }
            return count;
        }
        
        /*
        // generalized
        Bitboard getRayAttacks(Bitboard occupied, int dir8, int square)
        {

            Bitboard attacks = rayAttacks[dir8,square];
            Bitboard blocker = attacks & occupied;
            if (blocker!=0)
            {
                square = bitScan(blocker, isNegative(dir8));
                attacks ^= rayAttacks[dir8][square];
            }

            return attacks;
        }
        */

        public static void DebugDisplay(Bitboard bb)
        {
            for (int i = 7; i >= 0; i--)
            {
                Console.WriteLine(" +---+---+---+---+---+---+---+---+");
                for (int j = 0; j < 8; j++)
                {
                    Bitboard sqMask = (Bitboard)1 << (i * 8 + j);
                    Console.Write(" | " + ((bb & sqMask)!=0 ? 'x':' '));
                }
                Console.WriteLine(" |");
            }
            Console.WriteLine(" +---+---+---+---+---+---+---+---+");
        }

        internal Bitboard bishopAttacks(Bitboard occupiedBB, int square)
        {
            Bitboard ne     = getPositiveRayAttacks(occupiedBB, 1, square);
            Bitboard se     = getNegativeRayAttacks(occupiedBB, 3, square);
            Bitboard sw     = getNegativeRayAttacks(occupiedBB, 5, square);
            Bitboard nw     = getPositiveRayAttacks(occupiedBB, 7, square);

            return ne | se | sw | nw;
        }

        internal Bitboard rookAttacks(Bitboard occupiedBB, int square)
        {
            Bitboard north  = getPositiveRayAttacks(occupiedBB, 0, square);
            Bitboard east   = getPositiveRayAttacks(occupiedBB, 2, square);
            Bitboard south  = getNegativeRayAttacks(occupiedBB, 4, square);
            Bitboard west   = getNegativeRayAttacks(occupiedBB, 6, square);

           return north | east | south | west;
        }

        internal static void DebugDisplay(Bitboard bb, string desc)
        {
            Console.WriteLine(desc);
            DebugDisplay(bb);
        }

        // rayAttacks[8][64] is a precalculated BB set
        // TODO: add a DisplayBitboard(Bitboard bb) method to debug
        //       and ensure BB const are correctly set
        Bitboard getPositiveRayAttacks(Bitboard occupied, int dir8, int square)
        {
            Bitboard attacks = rayAttacks[dir8,square];
            Bitboard blocker = attacks & occupied;
            BitScanForward(out square, blocker | 0x8000000000000000);
           //_BitScanForward64 (&square, blocker | C64(0x8000000000000000));
           return attacks ^ rayAttacks[dir8,square];
        }
        // branchless (better for 64 processors)
        // TODO: CHANGE THE IMPLEMENTATION OF THIS METHOD
        Bitboard getNegativeRayAttacks(Bitboard occupied, int dir8, int square)
        {
            Bitboard attacks = rayAttacks[dir8, square];
            Bitboard blocker = attacks & occupied;
            BitScanReverse(out square, blocker | 1);
            //_BitScanReverse(&square, blocker | 1 ); // usable directly from Microsoft C++ compiler
            return attacks ^ rayAttacks[dir8, square];
        }
    }
}
